package be.msec.smartcard;

import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.AESKey;
import javacard.security.KeyBuilder;
import javacard.security.RSAPrivateKey;
import javacard.security.RSAPublicKey;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;


public class IdentityCard extends Applet {
	private final static byte IDENTITY_CARD_CLA =(byte)0x80;
	
	private static final byte VALIDATE_PIN_INS = 0x22;
	private static final byte GET_NAME_INS = 0x24;
	private static final byte AUTH_SP_INS = 0x25;
	private static final byte GET_SERIAL_INS = 0x26;
	private final static byte GET_SIGN_INS =0x28;
	private final static byte GET_ATTEMPTS_INS =0x30;
	private final static byte PUT_TIME_INS = 0x31;
	private final static byte PUT_TIME_SIGNATURE_INS = 0x32;
	private static final byte CHECK_CHALLENGE_INS = 0x36;
	private static final byte AUTH_CARD_INS = 0x37;
	private static final byte GET_ALL_ATTRIBUTES = 0x38;
	private static final byte GET_TIME_REVALIDATION_REQUIRED = 0x39;
	
	private final static byte PIN_TRY_LIMIT =(byte)0x03;
	private final static byte PIN_SIZE =(byte)0x04;
	
	private final static short SW_VERIFICATION_FAILED = 0x6300;
	private final static short SW_PIN_VERIFICATION_REQUIRED = 0x6301;

	private byte[] sp_cert_bytes;
	
	private byte[] common_cert_bytes = new byte[]{(byte) 12,(byte) 70,(byte) 111,(byte) 114,(byte) 32,(byte) 99,(byte) 111,(byte) 109,(byte) 109,(byte) 111,(byte) 110,(byte) 32,(byte) 117,(byte) 115,(byte) 101,(byte) 0,(byte) 67,(byte) 65,(byte) 0,(byte) 7,(byte) -30,(byte) 3,(byte) 10,(byte) 0,(byte) 65,(byte) 0,(byte) 3,(byte) 0,(byte) -53,(byte) 14,(byte) 63,(byte) 14,(byte) 59,(byte) 39,(byte) 44,(byte) -3,(byte) -28,(byte) -5,(byte) 104,(byte) 123,(byte) 19,(byte) 114,(byte) 73,(byte) -96,(byte) -16,(byte) 93,(byte) -90,(byte) 16,(byte) 72,(byte) -103,(byte) -6,(byte) -15,(byte) 30,(byte) 42,(byte) -38,(byte) -56,(byte) -41,(byte) 105,(byte) -124,(byte) 44,(byte) -10,(byte) 86,(byte) 2,(byte) -55,(byte) -60,(byte) 48,(byte) -82,(byte) 100,(byte) 94,(byte) 54,(byte) -105,(byte) -122,(byte) -49,(byte) 18,(byte) 37,(byte) -41,(byte) -55,(byte) 44,(byte) 99,(byte) 62,(byte) -56,(byte) -126,(byte) -40,(byte) 120,(byte) 85,(byte) 114,(byte) -41,(byte) 8,(byte) 29,(byte) -113,(byte) 95,(byte) 109,(byte) 1,(byte) 0,(byte) 1,(byte) 0,(byte) 64,(byte) 55,(byte) 87,(byte) 117,(byte) -110,(byte) -56,(byte) 113,(byte) -28,(byte) 26,(byte) 89,(byte) -2,(byte) 96,(byte) -43,(byte) -66,(byte) 35,(byte) 39,(byte) 105,(byte) 34,(byte) 28,(byte) 55,(byte) -45,(byte) 33,(byte) -26,(byte) 108,(byte) -41,(byte) 47,(byte) -41,(byte) -31,(byte) -55,(byte) 79,(byte) -115,(byte) 64,(byte) 12,(byte) -34,(byte) 87,(byte) -128,(byte) 126,(byte) 37,(byte) -32,(byte) 70,(byte) 49,(byte) 94,(byte) 6,(byte) 92,(byte) -100,(byte) 18,(byte) -126,(byte) 31,(byte) 123,(byte) 109,(byte) -43,(byte) -32,(byte) 102,(byte) -54,(byte) 45,(byte) -121,(byte) 51,(byte) 102,(byte) 23,(byte) 35,(byte) -86,(byte) 77,(byte) -26,(byte) 13,(byte) -45}; //niet meer geldig
	private Cert common_cert = null;
	private Cert sp_cert = null;
	
	private byte[] government_publicKey_modulus = new byte[]{(byte) -44,(byte) 127,(byte) 42,(byte) 97,(byte) -72,(byte) 91,(byte) -37,(byte) 98,(byte) 3,(byte) 105,(byte) -92,(byte) 63,(byte) 57,(byte) -117,(byte) -78,(byte) -7,(byte) -48,(byte) 41,(byte) -110,(byte) -101,(byte) -59,(byte) 56,(byte) 105,(byte) 29,(byte) -125,(byte) -3,(byte) 53,(byte) -50,(byte) 50,(byte) -7,(byte) 25,(byte) 60,(byte) -79,(byte) -71,(byte) 101,(byte) -75,(byte) 72,(byte) 72,(byte) -46,(byte) 16,(byte) -53,(byte) 122,(byte) -14,(byte) -112,(byte) -10,(byte) -24,(byte) 111,(byte) -9,(byte) 85,(byte) -71,(byte) -86,(byte) -8,(byte) -47,(byte) -125,(byte) 71,(byte) 19,(byte) -106,(byte) 30,(byte) 105,(byte) -34,(byte) 72,(byte) 29,(byte) 117,(byte) -53};
	private byte[] government_publicKey_exponent = new byte[] {(byte) 1,(byte) 0,(byte) 1};
	
	private byte[] ca_publicKey_modulus = new byte[]{(byte) -104,(byte) -5,(byte) 101,(byte) -78,(byte) 19,(byte) 47,(byte) -102,(byte) -84,(byte) 109,(byte) -69,(byte) 14,(byte) -79,(byte) 85,(byte) -127,(byte) -50,(byte) -53,(byte) 19,(byte) -44,(byte) -90,(byte) -79,(byte) 122,(byte) 49,(byte) -8,(byte) 5,(byte) -5,(byte) 58,(byte) 21,(byte) 35,(byte) -120,(byte) -104,(byte) -34,(byte) -58,(byte) 95,(byte) 80,(byte) -31,(byte) 100,(byte) 46,(byte) 12,(byte) 97,(byte) -16,(byte) -60,(byte) -10,(byte) 36,(byte) 56,(byte) -45,(byte) 45,(byte) -59,(byte) -51,(byte) -59,(byte) 21,(byte) -90,(byte) 119,(byte) 51,(byte) 40,(byte) -94,(byte) 0,(byte) 83,(byte) 42,(byte) 38,(byte) 43,(byte) 90,(byte) 44,(byte) 27,(byte) 33};
	private byte[] ca_publicKey_exponent = new byte[]{(byte) 1,(byte) 0,(byte) 1};
	
	private byte[] common_privateKey_modulus = new byte[] {(byte) -53, (byte) 14, (byte) 63, (byte) 14, (byte) 59, (byte) 39, (byte) 44, (byte) -3, (byte) -28, (byte) -5, (byte) 104, (byte) 123, (byte) 19, (byte) 114, (byte) 73, (byte) -96, (byte) -16, (byte) 93, (byte) -90, (byte) 16, (byte) 72, (byte) -103, (byte) -6, (byte) -15, (byte) 30, (byte) 42, (byte) -38, (byte) -56, (byte) -41, (byte) 105, (byte) -124, (byte) 44, (byte) -10, (byte) 86, (byte) 2, (byte) -55, (byte) -60, (byte) 48, (byte) -82, (byte) 100, (byte) 94, (byte) 54, (byte) -105, (byte) -122, (byte) -49, (byte) 18, (byte) 37, (byte) -41, (byte) -55, (byte) 44, (byte) 99, (byte) 62, (byte) -56, (byte) -126, (byte) -40, (byte) 120, (byte) 85, (byte) 114, (byte) -41, (byte) 8, (byte) 29, (byte) -113, (byte) 95, (byte) 109};
	private byte[] common_privateKey_exponent = new byte[] {(byte) -62, (byte) 52, (byte) 37, (byte) -22, (byte) -98, (byte) 120, (byte) -95, (byte) 119, (byte) 83, (byte) 1, (byte) -128, (byte) -77, (byte) -11, (byte) -83, (byte) 96, (byte) -18, (byte) 35, (byte) -11, (byte) -19, (byte) 85, (byte) -75, (byte) -102, (byte) 79, (byte) 127, (byte) 60, (byte) -77, (byte) 15, (byte) -4, (byte) -10, (byte) -55, (byte) 92, (byte) 55, (byte) 80, (byte) 96, (byte) 13, (byte) -15, (byte) -12, (byte) -91, (byte) -125, (byte) 22, (byte) 87, (byte) -48, (byte) 98, (byte) -121, (byte) -14, (byte) -121, (byte) 28, (byte) -56, (byte) 58, (byte) 101, (byte) 72, (byte) 99, (byte) -107, (byte) 65, (byte) 93, (byte) 43, (byte) 112, (byte) 54, (byte) 36, (byte) -96, (byte) -82, (byte) 90, (byte) -56, (byte) 1};
	
	private short lastChallenge = 0;
	private AESKey symKey;
	private AESKey ku;
	private boolean authenticated = false;
	
	private byte[] serial = new byte[]{(byte)0x4A, (byte)0x61, (byte)0x6e};
	private byte[] name = new byte[]{0x4A, 0x61, 0x6E, 0x20, 0x56, 0x6F, 0x73, 0x73, 0x61, 0x65, 0x72, 0x74};
	private OwnerPIN pin;
	private short attmptsNeeded;
	
	private static final byte DOMAIN_CA = (byte)0;
	private static final byte DOMAIN_GOV = (byte)1;
	private static final byte DOMAIN_EGOV = (byte)10;
	private static final byte DOMAIN_SOCIAL = (byte)11;
	private static final byte DOMAIN_COMMON = (byte)12;
	
	private byte[] lastValidationTime_bytes;	//Stores the temporary lastValidationTime received from the middleware
	private Date lastValidationTime;
	
	private IdentityCard() {
		/*
		 * During instantiation of the applet, all objects are created.
		 * In this example, this is the 'pin' object.
		 */
		pin = new OwnerPIN(PIN_TRY_LIMIT,PIN_SIZE);
		pin.update(new byte[]{0x01,0x02,0x03,0x04},(short) 0, PIN_SIZE);
		common_cert = Cert.FromByteArray(common_cert_bytes);
		RandomData randomData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);
        byte[] rnd = JCSystem.makeTransientByteArray((short)16, JCSystem.CLEAR_ON_RESET);
        randomData.generateData(rnd, (short)0, (short)rnd.length);
        ku = (AESKey) KeyBuilder.buildKey (KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
        ku.setKey(rnd, (short)0);	
		/*
		 * This method registers the applet with the JCRE on the card.
		 */
		register();
	}

	/*
	 * This method is called by the JCRE when installing the applet on the card.
	 */
	public static void install(byte bArray[], short bOffset, byte bLength)
			throws ISOException {
		new IdentityCard();
	}
	
	/*
	 * If no tries are remaining, the applet refuses selection.
	 * The card can, therefore, no longer be used for identification.
	 */
	public boolean select() {
		if (pin.getTriesRemaining()==0)
			return false;
		return true;
	}

	/*
	 * This method is called when the applet is selected and an APDU arrives.
	 */
	public void process(APDU apdu) throws ISOException {
		while(true) {
		//A reference to the buffer, where the APDU data is stored, is retrieved.
		byte[] buffer = apdu.getBuffer();
		
		//If the APDU selects the applet, no further processing is required.
		if(this.selectingApplet())
			return;
		
		//Check whether the indicated class of instructions is compatible with this applet.
		if (buffer[ISO7816.OFFSET_CLA] != IDENTITY_CARD_CLA)ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		//A switch statement is used to select a method depending on the instruction
		switch(buffer[ISO7816.OFFSET_INS]){
		case VALIDATE_PIN_INS:
			validatePIN(apdu);
			break;
		case GET_SERIAL_INS:
			getSerial(apdu);
			break;
		case GET_NAME_INS:
			getName(apdu);
			break;
		case GET_SIGN_INS:
			GetRSA(apdu);
			break;
		case GET_ATTEMPTS_INS:
			GetCertLength(apdu);
			break;
		case PUT_TIME_INS:
			ReceiveTime(apdu);
			break;
		case PUT_TIME_SIGNATURE_INS:
			ReceiveTimeSignature(apdu);
			break;
		case AUTH_SP_INS:
			AuthenticateSP(apdu);
			break;
		case CHECK_CHALLENGE_INS:
			checkChallenge(apdu);
			break;
		case AUTH_CARD_INS:
			authenticateCard(apdu);
			break;
		case GET_ALL_ATTRIBUTES:
			getAllAttributes(apdu);
			break;
		case GET_TIME_REVALIDATION_REQUIRED:
			getTimeValidationRequired(apdu);
			break;
		
		//If no matching instructions are found it is indicated in the status word of the response.
		//This can be done by using this method. As an argument a short is given that indicates
		//the type of warning. There are several predefined warnings in the 'ISO7816' class.
		default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}
		}
	}

	private void getAllAttributes(APDU apdu) {
		byte[] buffer = apdu.getBuffer();
		if(buffer[ISO7816.OFFSET_LC]==PIN_SIZE){
			apdu.setIncomingAndReceive();
			if (pin.check(buffer, ISO7816.OFFSET_CDATA,PIN_SIZE)==false)
				ISOException.throwIt(SW_VERIFICATION_FAILED);
		}else ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		
		apdu.setOutgoing();

		byte[] ku_bytes = new byte[16];
		ku.getKey(ku_bytes, (short)0);
		byte[] nymUSP = new byte[ku_bytes.length + sp_cert.subject.length];
		Util.arrayCopy(ku_bytes, (short)0, nymUSP, (short)0, (short)ku_bytes.length);
		Util.arrayCopy(sp_cert.subject, (short)0, nymUSP, (short)ku_bytes.length, (short)sp_cert.subject.length);
		Cipher symCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
		if(symKey.isInitialized()) {
			byte[] ivdata = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
			symCipher.init(symKey, Cipher.MODE_ENCRYPT, ivdata, (short)0, (short)ivdata.length);
		}
		
		/***** Retrieving the attributes *****/
		byte[] attributes;
		if(authenticated) {
			byte domain = sp_cert.GetDomain();
			switch(domain) {
				case DOMAIN_EGOV: attributes = UserInfo.GeteGovAtributes();
								  break;
				case DOMAIN_SOCIAL: attributes = UserInfo.GetSocialAtributes();
								  break;
				default: attributes = UserInfo.GetDefaultAtributes();
								  break;
			}
		}
		else{
			attributes = UserInfo.error;
		}
		
		byte[] data = new byte[attributes.length + nymUSP.length + 2];
		Util.arrayCopy(attributes, (short)0, data, (short)0, (short)attributes.length);
		Util.arrayCopy(shortToByteArray((short)nymUSP.length), (short)0, data, (short)attributes.length, (short)2);
		Util.arrayCopy(nymUSP, (short)0, data, (short)(attributes.length + 2), (short)nymUSP.length);
		
		/***** data must be block aligned before it can be encrypted *****/
		int i=0;
		do {
			i += 16;
		} while(i<data.length);
		byte[] output = new byte[i];	//Create block aligned byte array ( must be in steps of 16 bytes for 128bit symmetric key)
		Util.arrayCopy(data, (short)0, output, (short)0, (short)data.length);
		
		 /***** Encrypting outgoing data *****/
		byte[] outputEncrypted= new byte[i];
		symCipher.doFinal(output, (short)0, (short)output.length, outputEncrypted, (short)0);  //update(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) 
		
		apdu.setOutgoingLength((short) outputEncrypted.length);
		apdu.sendBytesLong(outputEncrypted, (short)0, (short)outputEncrypted.length);
	}
	
	private void authenticateCard(APDU apdu) {
		//Challenge ontvangen
		byte[] challenge = readLargeInputBuffer(apdu);
		
		if(authenticated== false) {
			apdu.setOutgoingLength((short) 3);
			apdu.sendBytesLong(new byte[] { (byte)'N', (byte)'o', (byte)'k'}, (short)0, (short)3);	//Nok
		}
		
		//Challenge decrypteren
		Cipher symCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
		if(symKey.isInitialized()) {
			byte[] ivdata = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
			symCipher.init(symKey, Cipher.MODE_DECRYPT, ivdata, (short)0, (short)ivdata.length);
		}
		byte[] result = new byte[16];
		symCipher.doFinal(challenge, (short)0, (short)challenge.length, result, (short)0); //doFinal(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) 
		
		//Response samenstellen: cert + signature
		byte[] signature = signWithCommon(result,(short)64);
		byte[] output = new byte[(short)(256)];
		Util.arrayCopy(signature, (short)0, output, (short)0, (short)signature.length);
		Util.arrayCopy(common_cert_bytes, (short)0, output, (short)(signature.length ), (short)common_cert_bytes.length);
		
		//Response encrypteren
		symCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
		if(symKey.isInitialized()) {
			byte[] ivdata = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
			symCipher.init(symKey, Cipher.MODE_ENCRYPT, ivdata, (short)0, (short)ivdata.length);
		}
		
		byte[] outputEncrypted= new byte[256];
		symCipher.doFinal(output, (short)0, (short)output.length, outputEncrypted, (short)0);  //update(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) 

		apdu.setOutgoing();
		apdu.setOutgoingLength((short)outputEncrypted.length);
		apdu.sendBytesLong(outputEncrypted,(short)0, (short)outputEncrypted.length);	
		
	}
	
	public static byte[] shortToByteArray(short s) {
		return new byte[] { (byte) ((s & 0xFF00) >> 8), (byte) (s & 0x00FF) };
	}
	
	private byte[] readLargeInputBuffer(APDU apdu) {
		byte[] buffer = apdu.getBuffer();
		short bytesLeft = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
		byte[] storage = new byte[bytesLeft];
		Util.arrayCopy(buffer, (short)0, storage, (short)0, (short)5);
		short readCount = apdu.setIncomingAndReceive();
		short i = 0; //ISO7816.OFFSET_CDATA;
		while ( bytesLeft > 0){
			Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, storage, i, readCount);
			bytesLeft -= readCount;
			i+=readCount;
			readCount = apdu.receiveBytes(ISO7816.OFFSET_CDATA);
		}
		return storage;
	}
	
	private void checkChallenge(APDU apdu) {
		byte[] storage = readLargeInputBuffer(apdu);
		
		Cipher symCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
		if(symKey.isInitialized()) {
			byte[] ivdata = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
			symCipher.init(symKey, Cipher.MODE_DECRYPT, ivdata, (short)0, (short)ivdata.length);
		}
		byte[] result = new byte[16];
		symCipher.doFinal(storage, (short)0, (short)storage.length, result, (short)0); //doFinal(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) 
		
		short receivedChallenge = readShort(result, 0);
		
		apdu.setOutgoing();
		if(receivedChallenge == (lastChallenge+1)) {
			authenticated = true;
			apdu.setOutgoingLength((short) 2);
			apdu.sendBytesLong(new byte[] { (byte)'O', (byte)'k'}, (short)0, (short)2);	//Ok
		}else{
			apdu.setOutgoingLength((short) 3);
			apdu.sendBytesLong(new byte[] { (byte)'N', (byte)'o', (byte)'k'}, (short)0, (short)3);	//Nok
		}
		
	}

	private void AuthenticateSP(APDU apdu) {		
		/* Certificaat ontvangen*/
		byte[] storage = readLargeInputBuffer(apdu);
		sp_cert_bytes = storage;	
		
		/*Certificaat array aanmaken en signature eruit halen*/
		Cert cert = Cert.FromByteArray(sp_cert_bytes);
		sp_cert = cert;
		
		/*Geldigheid certificaat controleren*/
		//Create the public key of the government
		RSAPublicKey publicKey = (RSAPublicKey) KeyBuilder.buildKey (KeyBuilder.TYPE_RSA_PUBLIC, (short)512 , false) ;
		publicKey.setExponent(ca_publicKey_exponent, (short)0, (short)ca_publicKey_exponent.length);
		publicKey.setModulus(ca_publicKey_modulus, (short)0, (short)ca_publicKey_modulus.length);
		
		boolean verify = cert.Verify(publicKey);
		
		if(verify) {
            RandomData randomData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);
            byte[] rnd = JCSystem.makeTransientByteArray((short)16, JCSystem.CLEAR_ON_RESET);
            randomData.generateData(rnd, (short)0, (short)rnd.length);
            symKey = (AESKey) KeyBuilder.buildKey (KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
            symKey.setKey(rnd, (short)0);	
			
			//Symetrische key encrypteren met asymetrische public key
			Cipher asymCipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
			asymCipher.init(cert.publicKey, Cipher.MODE_ENCRYPT);
			byte[] encryptedKey = new byte[64]; 
			asymCipher.doFinal(rnd, (short)0, (short)rnd.length, encryptedKey, (short)0); 
			//update(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) 
		
			//Make challenge
			byte[] challenge = new byte[2];
			randomData.generateData(challenge, (short)0, (short)challenge.length);
			lastChallenge =(short)( ((challenge[0]&0xFF)<<8) | (challenge[1]&0xFF) ); //Challenge bewaren om later te kunnen checken
			byte[] subject = cert.subject;
			byte[] c = new byte[(short)48];		
			Util.arrayCopy(challenge, (short)0, c, (short)0, (short)challenge.length);
			Util.arrayCopy(subject, (short)0, c, (short)challenge.length, (short)subject.length);
			
			//Challenge symetrisch encrypteren met symetrische AES key
			Cipher symCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
			if(symKey.isInitialized()) {
				byte[] ivdata = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
				symCipher.init(symKey, Cipher.MODE_ENCRYPT, ivdata, (short)0, (short)ivdata.length);
			}
			
			byte[] encryptedC= new byte[48];
			symCipher.doFinal(c, (short)0, (short)c.length, encryptedC, (short)0); //update(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) 		
			
			//SymKey + Challenge terugsturen naar middleware
			byte[] output = new byte[(short)(encryptedC.length+encryptedKey.length)];
			Util.arrayCopy(encryptedKey, (short)0, output, (short)0, (short)encryptedKey.length);
			Util.arrayCopy(encryptedC, (short)0, output, (short)encryptedKey.length, (short)encryptedC.length);
			apdu.setOutgoing();
			apdu.setOutgoingLength((short)output.length);
			apdu.sendBytesLong(output, (short)0, (short)output.length);
		}
	}
	
	public static short readShort(byte[] data, int offset) {
		return (short) (((data[offset] << 8)) | ((data[offset + 1] & 0xff)));
	}
	
	private void ReceiveTimeSignature(APDU apdu) {
		byte[] buffer = apdu.getBuffer();
		apdu.setIncomingAndReceive();		
		short sigLength = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
		
		//Create the public key of the government
		RSAPublicKey publicKey = (RSAPublicKey) KeyBuilder.buildKey (KeyBuilder.TYPE_RSA_PUBLIC, (short)512 , false) ;
		publicKey.setExponent(government_publicKey_exponent, (short)0, (short)government_publicKey_exponent.length);
		publicKey.setModulus(government_publicKey_modulus, (short)0, (short)government_publicKey_modulus.length);
		
		//Verify the received signature
		Signature signature = Signature.getInstance (Signature.ALG_RSA_SHA_PKCS1, false) ;
		signature.init(publicKey, Signature.MODE_VERIFY) ;
		boolean verify = signature.verify(
								this.lastValidationTime_bytes, 					// data to be verified
								(short)0, 										// offset
								(short)this.lastValidationTime_bytes.length, 	// data Length 
								buffer, 										// signature bytes
								ISO7816.OFFSET_CDATA, 							// Offset 
								sigLength);										// signature length
		if(verify) {
			this.lastValidationTime = new Date(lastValidationTime_bytes, (byte)0);
		}
		//Send response to middleware
		apdu.setOutgoing();
		if(verify) {
			apdu.setOutgoingLength((short) 2);
			apdu.sendBytesLong(new byte[] { (byte)'O', (byte)'k'}, (short)0, (short)2);	//Ok
		}else{
			apdu.setOutgoingLength((short) 3);
			apdu.sendBytesLong(new byte[] { (byte)'N', (byte)'o', (byte)'k'}, (short)0, (short)3);	//Nok
		}
	}

	private void ReceiveTime(APDU apdu) {	
		//Read date from APDU
		byte[] buffer = apdu.getBuffer();
		apdu.setIncomingAndReceive();
		short dateLength = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
		this.lastValidationTime_bytes = new byte[dateLength];
		
		//Store in lastValidationTime_bytes
		Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, lastValidationTime_bytes, (short)0, (short)dateLength);
		
		//Send response "Ok"
		apdu.setOutgoing();
		apdu.setOutgoingLength((short) 2);
		apdu.sendBytesLong(new byte[] { (byte)'O', (byte)'k'}, (short)0, (short)2);
	}
	
	private void getTimeValidationRequired(APDU apdu) {
		//Next line is a dummy test
		//this.lastValidationTime = new Date(new byte[]{(byte)7, (byte)-35, (byte)3, (byte)12}, (byte)0);
		
		boolean revalidationRequired = false;
		
		if(lastValidationTime == null ) {
			revalidationRequired = true;
		}
		else{
			byte[] buffer = apdu.getBuffer();
			apdu.setIncomingAndReceive();					
			Date receivedDate = new Date(buffer, (byte)ISO7816.OFFSET_CDATA);
			revalidationRequired = lastValidationTime.RevalidationNeeded(receivedDate);
		}
		
		if(revalidationRequired) {
			apdu.setOutgoing();
			apdu.setOutgoingLength((short)1);
			apdu.sendBytesLong(new byte[]{'y'}, (short)0, (short)1);	//yes
		}else{
			apdu.setOutgoing();
			apdu.setOutgoingLength((short)1);
			apdu.sendBytesLong(new byte[]{'n'}, (short)0, (short)1);	//no
		}
	}
	
	/*
	 * This method is used to authenticate the owner of the card using a PIN code.
	 */
	private void validatePIN(APDU apdu){
		byte[] buffer = apdu.getBuffer();
		//The input data needs to be of length 'PIN_SIZE'.
		//Note that the byte values in the Lc and Le fields represent values between
		//0 and 255. Therefore, if a short representation is required, the following
		//code needs to be used: short Lc = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
		if(buffer[ISO7816.OFFSET_LC]==PIN_SIZE){
			//This method is used to copy the incoming data in the APDU buffer.
			apdu.setIncomingAndReceive();
			//Note that the incoming APDU data size may be bigger than the APDU buffer 
			//size and may, therefore, need to be read in portions by the applet. 
			//Most recent smart cards, however, have buffers that can contain the maximum
			//data size. This can be found in the smart card specifications.
			//If the buffer is not large enough, the following method can be used:
			//
			//byte[] buffer = apdu.getBuffer();
			//short bytesLeft = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
			//Util.arrayCopy(buffer, START, storage, START, (short)5);
			//short readCount = apdu.setIncomingAndReceive();
			//short i = ISO7816.OFFSET_CDATA;
			//while ( bytesLeft > 0){
			//	Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, storage, i, readCount);
			//	bytesLeft -= readCount;
			//	i+=readCount;
			//	readCount = apdu.receiveBytes(ISO7816.OFFSET_CDATA);
			//}
			if (pin.check(buffer, ISO7816.OFFSET_CDATA,PIN_SIZE)==false)
				ISOException.throwIt(SW_VERIFICATION_FAILED);
		}else ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
	}
	
	/*
	 * This method checks whether the user is authenticated and sends
	 * the serial number.
	 */
	private void getSerial(APDU apdu){
		//If the pin is not validated, a response APDU with the
		//'SW_PIN_VERIFICATION_REQUIRED' status word is transmitted.
		if(!pin.isValidated())ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
		else{
			//This sequence of three methods sends the data contained in
			//'serial' with offset '0' and length 'serial.length'
			//to the host application.
			apdu.setOutgoing();
			apdu.setOutgoingLength((short)serial.length);
			apdu.sendBytesLong(serial,(short)0,(short)serial.length);
		}
	}
	
	private void GetCertLength(APDU apdu){
		//If the pin is not validated, a response APDU with the
		//'SW_PIN_VERIFICATION_REQUIRED' status word is transmitted.
		if(!pin.isValidated())ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
		else{
			//This sequence of three methods sends the data contained in
			//'serial' with offset '0' and length 'serial.length'
			//to the host application.
			
			int length = common_cert_bytes.length;
			attmptsNeeded = (short)(length/240);
			if(attmptsNeeded * 240 < common_cert_bytes.length) {
				attmptsNeeded++;
			}
			apdu.setOutgoing();
			apdu.setOutgoingLength((short)1);
			apdu.sendBytesLong(new byte[] {(byte)99}, (short)0, (short)1);
		}
	}
	
	private void getName(APDU apdu){
		//If the pin is not validated, a response APDU with the
		//'SW_PIN_VERIFICATION_REQUIRED' status word is transmitted.
		if(!pin.isValidated())ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
		else{
			//This sequence of three methods sends the data contained in
			//'serial' with offset '0' and length 'serial.length'
			//to the host application.
			apdu.setOutgoing();
			apdu.setOutgoingLength((short)name.length);
			apdu.sendBytesLong(name,(short)0,(short)name.length);
		}
	}
	
	private byte[] signWithCommon(byte[] buffer, short keySizeInBytes) {
		short offset = 0;
		short keySizeInBits = 512;
		byte[] output = new byte[keySizeInBytes];

		RSAPrivateKey privKey = (RSAPrivateKey) KeyBuilder.buildKey (KeyBuilder.TYPE_RSA_PRIVATE, keySizeInBits , false) ;
		privKey.setExponent(common_privateKey_exponent, offset , (short)common_privateKey_exponent.length);
		privKey.setModulus(common_privateKey_modulus, offset , (short)common_privateKey_modulus.length);	
		
		Signature signature = Signature.getInstance (Signature.ALG_RSA_SHA_PKCS1, false) ;
		signature.init( privKey , Signature .MODE_SIGN) ;
		signature.sign(buffer , (short)0 , (short)buffer.length , output , (short)0);
		//(byte[] inBuff, short inOffset, short inLength, byte[] sigBuff, short sigOffset) 
		return output;
	}
	
	private void GetRSA(APDU apdu) {
		byte[] buffer = apdu.getBuffer();
		apdu.setIncomingAndReceive();

		byte[] output = signWithCommon(buffer, (short)64);
		
		apdu.setOutgoing();
		apdu.setOutgoingLength((short)output.length);
		apdu.sendBytesLong(output,(short)0, (short)output.length);					
	}
}

